package com.legendframework.core.dao.transaction;

import com.legendframework.core.aop.AbstractProxy;
import com.legendframework.core.dao.annotation.Transactional;
import com.legendframework.core.ioc.enums.Propagation;
import com.legendframework.core.dao.Return;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

/**
 * 事务代理
 */
public class TransactionProxy extends AbstractProxy {

    //当前线程的事务链
    private final ThreadLocal<LinkedList<Return<String,Boolean>>> threadLocal = new ThreadLocal<>();

    private List<ITransaction> transactions;

    public TransactionProxy() {
        this.transactions = new ArrayList<>();
    }

    /**
     * 新增事务模版
     * @param transaction
     * @return
     */
    public TransactionProxy addTransaction(ITransaction transaction){
        this.transactions.add(transaction);
        return this;
    }

    /**
     * 校验方法上是否有事务注解
     * @param method
     * @return
     */
    private boolean checkTransaction(Method method){
        return method.isAnnotationPresent(Transactional.class);
    }

    /**
     * 获取事务注解
     * @param method
     * @return
     */
    private Transactional getTransaction(Method method){
        return method.getAnnotation(Transactional.class);
    }

    /**
     * 当前线程是否开启事务
     * @return
     */
    private boolean isOpenTransaction(){
        LinkedList<Return<String, Boolean>> linkedList = threadLocal.get();
        if (linkedList == null) {
            threadLocal.set(new LinkedList<>());
            return false;
        }
        for (Return<String, Boolean> item : threadLocal.get()) {
            if (item != null && item.getR2()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void enhance(Object source, Method method, Object[] args) {
        if (!checkTransaction(method)) {
            return;
        }
        Transactional transaction = getTransaction(method);
        //是否开启事务
        boolean flag = false;
        if (isOpenTransaction()) {
            //有事务
            switch (transaction.propagation()){
                case REQUIRES_NEW:
                    flag = true;
                    break;
                case NOT_SUPPORTED:
                    //挂载当前事务
                    transactions.forEach(ITransaction::onMount);
            }
        }else {
            //没有事务
            switch (transaction.propagation()){
                case REQUIRED:
                    flag = true;
                    break;
                case REQUIRES_NEW:
                    flag = true;
                    break;
            }
        }
        if (flag) {
            transactions.forEach(ITransaction::onStart);
        }
        threadLocal.get().addLast(Return.build(UUID.randomUUID().toString().replace("-",""),flag));
    }

    @Override
    public Object postposition(Object source, Method method, Object[] args, Object result) {
        if (!checkTransaction(method)) {
            return result;
        }
        Transactional transaction = getTransaction(method);
        if (transaction.propagation() == Propagation.NOT_SUPPORTED) {
            //解除事务挂载
            transactions.forEach(ITransaction::onRecovery);

        }
        //判断当前事务
        if (threadLocal.get().getLast().getR2()) {
            //有事务
            transactions.forEach(ITransaction::onCommit);
        }
        threadLocal.get().removeLast();
        return result;
    }

    @Override
    public Object exceptionHandle(Object source, Method method, Object[] args, Throwable e) throws Throwable {
        if (checkTransaction(method)) {
            Transactional transaction = getTransaction(method);
            if (transaction.propagation() == Propagation.NOT_SUPPORTED) {
                //解除事务挂载
                transactions.forEach(ITransaction::onRecovery);
            }
            if (threadLocal.get().getLast().getR2()) {
                transactions.forEach(ITransaction::onBack);
            }
            threadLocal.get().removeLast();
        }
        throw e;
    }
}
